package com.tongtech.system.service.impl;

import com.tongtech.common.datascope.annotation.DataScope;
import com.tongtech.common.security.utils.SecurityUtils;
import com.tongtech.system.api.domain.SysUser;
import com.tongtech.system.api.model.LoginUser;
import com.tongtech.system.domain.File;
import com.tongtech.system.enums.FileType;
import com.tongtech.system.mapper.FileMapper;
import com.tongtech.system.service.IFileService;
import com.tongtech.system.service.ISysUserService;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 文件管理 服务实现
 *
 * @author xingyc
 */
@Service
public class FileServiceImpl implements IFileService {

    private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class);

    @Value("${minio.url}")
    private String minioUrl;

    @Autowired
    private FileMapper fileMapper;

    @Autowired
    private ISysUserService userService;

    /**
     * 查询文件管理数据
     *
     * @param file 文件信息
     * @return 文件信息集合
     */
    @Override
    @DataScope(deptAlias = "d", userAlias = "u")
    public List<File> selectFileList(File file) {
        file.setCreateBy(SecurityUtils.getUsername());
        //继承父节点的permission属性（0-私有 1-公开
        setPermission(file);
        List<File> list = fileMapper.selectFileList(file);
        return list;
    }

    /**
     * 查询公共文件管理数据
     *
     * @param file 文件信息
     * @return 文件信息集合
     */
    @Override
    @DataScope(deptAlias = "d", userAlias = "u")
    public List<File> selectPublicFileList(File file) {
        file.setCreateBy(SecurityUtils.getUsername());
        //继承父节点的permission属性（0-私有 1-公开
        setPermission(file);
        List<File> list = fileMapper.selectPublicFileList(file);
        return list;
    }

    private void setPermission(File file){
        //继承父节点的permission属性（0-私有 1-公开）
        Long parentId = file.getParentId();
        if(parentId != -1L){
            File parentFile = selectFileById(parentId);
            if(Objects.nonNull(parentFile)){
                String permission = parentFile.getPermission();
                file.setPermission(permission);
            }
        }
    }

    /**
     * 根据文件ID查询信息
     *
     * @param fileId 文件ID
     * @return 文件信息
     */
    @Override
    public File selectFileById(Long fileId) {
        File file = fileMapper.selectFileById(fileId);
        return file;
    }

    /**
     * 新增保存文件信息
     *
     * @param file 文件信息
     * @return 结果
     */
    @Override
    public Long insertFile(File file) {
        if (Objects.nonNull(file)) {
            fillUserIdDeptId(file);
            file.setCreateBy(SecurityUtils.getUsername());
            setPermission(file);
        }
        int result = fileMapper.insertFile(file);
        return file.getFileId();
    }

    private void fillUserIdDeptId(File file){
        //补充userId和deptId，用于数据权限。
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if(Objects.nonNull(loginUser)){
            SysUser sysUser = loginUser.getSysUser();
            Long userId = sysUser.getUserId();
            Long deptId = sysUser.getDeptId();
            file.setUserId(userId);
            file.setDeptId(deptId);
        }
    }

    /**
     * 新增保存文件信息
     *
     * @param files 文件信息
     * @return 结果
     */
    @Override
    @Transactional
    public int insertFileBatch(List<File> files) {
        int count = 0;
        for (File file : files) {
            if (Objects.nonNull(file)) {
                fillUserIdDeptId(file);
                file.setCreateBy(SecurityUtils.getUsername());
                //继承父节点的permission属性（0-私有 1-公开）
                setPermission(file);
                fileMapper.insertFile(file);
                count++;
            }
        }
        return count;
    }

    /**
     * 修改保存文件信息
     *
     * @param file 文件信息
     * @return 结果
     */
    @Override
    public int updateFile(File file) {
        int result = fileMapper.updateFile(file);
        return result;
    }

    /**
     * 删除文件管理信息
     *
     * @param deptId 文件ID
     * @return 结果
     */
    @Override
    public int deleteFileById(Long deptId) {
        return fileMapper.deleteFileById(deptId);
    }

    public void downloadFromOSS(String parentPath, File f){
        URL url = null;
        try {
            log.info("minioUrl: {}", minioUrl);
            String fileUrl = minioUrl + f.getUrl();
            url = new URL(fileUrl);
            URLConnection conn = url.openConnection();
            InputStream inputStream = conn.getInputStream();
            String filePath = parentPath + "/" + f.getFileName();
            java.io.File file = new java.io.File(filePath);
            FileUtils.copyInputStreamToFile(inputStream, file);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 递归查询该文件及其子目录
     *
     * @param parentId 文件ID
     * @return 结果
     */
    public void dirs(Long parentId, java.io.File folder, String parentPath) {
        File queryFile = new File();
        queryFile.setParentId(parentId);
        List<File> list = fileMapper.selectFileList(queryFile);
        if (!CollectionUtils.isEmpty(list)) {
            for (File f : list) {
                Byte fileType = f.getFileType();
                if (Objects.equals(FileType.FILE.getCode(), fileType)) {
                    //若是文件，则从对象存储下载该文件
                    downloadFromOSS(parentPath, f);
                } else {
                    //若为文件夹，则创建文件夹
                    String subPath = parentPath + "/" + f.getFileName();
                    java.io.File subFolder = new java.io.File(subPath);
                    subFolder.mkdir();
                    dirs(f.getFileId(), subFolder, subPath);
                }
            }
        } else {
            return;
        }
    }

    /**
     * 下载文件，包含其子文件
     *
     * @param fileId   文件ID
     * @param response 响应对象
     * @return 结果
     */
    @Override
    public void download(Long fileId, HttpServletResponse response) {
        //将该文件夹下所有文件及子文件夹都下载，打成zip包返回给浏览器
        File dbFile = selectFileById(fileId);
        String fileName = dbFile.getFileName();
        String sourceFileName = "/data/service/runtime/support/" + fileName;
        java.io.File folder = new java.io.File(sourceFileName);
        folder.mkdir();
        //判断当前文件是文件夹还是文件
        if(Objects.equals(FileType.FILE.getCode(), dbFile.getFileType())){
            downloadFromOSS(sourceFileName, dbFile);
        }else{
            //递归检查该文件及其子目录
            dirs(fileId, folder, sourceFileName);
        }
        // 创建一个输出流将数据写入ZIP文件
        String zipName = sourceFileName + ".zip";
        InputStream inputStream = null;
        ServletOutputStream outputStream = null;
        try {
            zipDirectory(sourceFileName, zipName);
            inputStream = new FileInputStream(zipName);// 文件的存放路径
            response.reset();

            response.setCharacterEncoding("UTF-8");
            // 这步很重要，需要在给前端返回的请求头中添加Content-Disposition字段，否则前端会取不到Content-Disposition的信息
            response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName + ".zip", "UTF-8"));
            outputStream = response.getOutputStream();
            byte[] b = new byte[1024];
            int len;
            //从输入流中读取一定数量的字节，并将其存储在缓冲区字节数组中，读到末尾返回-1
            while ((len = inputStream.read(b)) > 0) {
                outputStream.write(b, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                outputStream.flush();
                outputStream.close();
                inputStream.close();
                folder.delete();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 下载文件，包含其子文件
     * TODO 为后续下载预留接口
     * @param fileId   文件ID
     * @param response 响应对象
     * @return 结果
     */
    @Override
    public InputStream downloadFromMinio(Long fileId, HttpServletResponse response) throws Exception{
        MinioClient minioClient = MinioClient.builder()
                .endpoint(minioUrl)
                .credentials("", "")
                .build();

        return minioClient.getObject(GetObjectArgs.builder()
                .bucket("")
                .object("")
                .build());
    }

    public static void zipDirectory(String sourceDir, String zipFile) throws IOException {
        FileOutputStream fos = new FileOutputStream(zipFile);
        ZipOutputStream zos = new ZipOutputStream(fos);
        java.io.File directory = new java.io.File(sourceDir);
        zip(directory, directory.getName(), zos);
        zos.close();
        fos.close();
    }

    private static void zip(java.io.File fileToZip, String fileName, ZipOutputStream zos) throws IOException {
        if (fileToZip.isHidden()) {
            return;
        }
        if (fileToZip.isDirectory()) {
            if (fileName.endsWith("/")) {
                zos.putNextEntry(new ZipEntry(fileName));
            } else {
                zos.putNextEntry(new ZipEntry(fileName + "/"));
            }
            zos.closeEntry();
            java.io.File[] children = fileToZip.listFiles();
            for (java.io.File child : children) {
                zip(child, fileName + "/" + child.getName(), zos);
            }
            return;
        }
        FileInputStream fis = new FileInputStream(fileToZip);
        ZipEntry zipEntry = new ZipEntry(fileName);
        zos.putNextEntry(zipEntry);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = fis.read(buffer)) >= 0) {
            zos.write(buffer, 0, length);
        }
        fis.close();
        zos.closeEntry();
    }
}
